iT邦幫忙

2024 iThome 鐵人賽

DAY 24
0

對應 30天挑戰精通 PowerShell 該書第 20 章。

看完這章後,我開始對既有運行的 script 有些想法,有種原來還能這樣用的想法,特別是驗證以及處理錯誤這塊,不過讓我們就先跟著書中的章節走下去,之後有機會再來分享。


20.1 起始點

書中提供了一個初始化例子,是來自前幾天的例子( 透過 Get-CimInstance cmdlet 獲取指定計算機上的邏輯磁碟資訊,並篩選出硬碟驅動器。它將結果依據設備ID排序,並以表格格式顯示每個磁碟的設備ID、可用空間 (以 MB 為單位)、總大小 (以 GB 為單位) 以及可用空間 ),然而這邊將原先的 Format-Table 改成用 Select-Object ,因為 Format-Table 用於將輸出直接格式化為表格形式,方便在控制台中查看。雖然這對於立即顯示很有用,但在指令碼或函式中使用並非最佳實踐,特別是當程式碼需要重複使用或進一步處理時。

為什麼使用 Select-Object

保留物件屬性

Select-Object 會建立包含選定屬性的物件,允許進一步的操作或管線處理。

彈性高

輸出仍然是結構化的資料,可以在管線中傳遞、匯出或進一步操作。

修正後的範例

param (
    $computername = 'localhost',
    $drivetype = 3
)

Get-CimInstance -class Win32_LogicalDisk -computername $computername `
    -Filter "drivetype=$drivetype" | 
    Sort-Object -Property DeviceID |
    Select-Object -Property DeviceID ,
    @{Label = 'FreeSpace(MB)'; expression = { $_.FreeSpace / 1MB -as [int] } },
    @{Label = 'Size(GB)'; expression = { $_.Size / 1GB -as [int] } },
    @{Label = '%Free'; expression = { $_.FreeSpace / $_.Size * 100 -as [int] } } 

20.2 讓 PowerShell 處理困難的工作

在指令碼中添加 [CmdletBinding()] 屬性,可以將其轉變為進階指令碼( advanced script ),使其功能和使用體驗更接近 PowerShell 的內建 cmdlet,從而提高指令碼的專業性和靈活性。

支援通用參數

添加 [CmdletBinding()] 後,指令碼自動支援 PowerShell 的通用參數,例如 -Verbose-Debug-ErrorAction 等,這些參數是 cmdlet 的標準特徵之一,能顯著增強指令碼的控制力和調試性。

優化後的指令碼案例

[CmdletBinding()]
param (
    [string]$computername = 'localhost',
    [int]$drivetype = 3
)

try {
    Get-CimInstance -Class Win32_LogicalDisk -ComputerName $computername -Filter "drivetype=$drivetype" -ErrorAction Stop -ErrorVariable GetCimError |
    Sort-Object -Property DeviceID |
    Select-Object -Property DeviceID,
        @{Label = 'FreeSpace(MB)'; Expression = { $_.FreeSpace / 1MB -as [int] } },
        @{Label = 'Size(GB)'; Expression = { $_.Size / 1GB -as [int] } },
        @{Label = '%Free'; Expression = { $_.FreeSpace / $_.Size * 100 -as [int] } }
}
catch {
    Write-Error "Failed to retrieve logical disk information for $computername. Details: $GetCimError"
}
  • -ErrorAction Stop:確保在遇到錯誤時即刻中止操作,以便進行更詳細的錯誤處理。
  • -ErrorVariable:將錯誤訊息儲存到指定變數 ( GetCimError) 中,這樣可以在 catch 區塊中顯示更詳細的錯誤資訊,有助於調試和排除故障。

增強參數管理功能

這個在稍後的 20.3 讓參數成為強制性的章節裡,會使用到。
使用 [CmdletBinding()] 可以讓指令碼支援更豐富的參數屬性,如 MandatoryPositionValueFromPipeline,這些屬性讓參數的管理和使用更加靈活與直觀。例如:

  • Mandatory:使參數成為必填項,避免因用戶輸入不完整而出現錯誤。
  • Position:讓參數具備順序,使輸入變得簡化,提升用戶體驗。
  • ValueFromPipeline:允許從管道中接收參數,方便與其他 cmdlet 組合使用。

20.3 讓參數成為強制性的

透過 [Parameter(Mandatory=$True)] 的設定,可以將參數標記為強制性,確保使用者在執行指令碼或函式時必須提供該參數的值。如果未提供該參數,PowerShell 會自動提示使用者輸入所需的值,從而避免因缺少必要參數而導致的執行錯誤。

修正後的範例

[CmdletBinding()]
param (
    [Parameter(Mandatory=$True, Position=0)]
    [string]$computername,

    [Parameter(Position=1)]
    [int]$drivetype = 3
)

Get-CimInstance -Class Win32_LogicalDisk -ComputerName $computername `
    -Filter "drivetype=$drivetype" | 
    Sort-Object -Property DeviceID |
    Select-Object -Property DeviceID,
        @{Label = 'FreeSpace(MB)'; Expression = { $_.FreeSpace / 1MB -as [int] } },
        @{Label = 'Size(GB)'; Expression = { $_.Size / 1GB -as [int] } },
        @{Label = '%Free'; Expression = { $_.FreeSpace / $_.Size * 100 -as [int] } }

解釋

在這段範例中, [Parameter(Mandatory=$True)] 的設定使 $computername 成為一個必填參數。當使用者執行這段指令碼時,必須提供 -computername 的值,否則 PowerShell 會自動提示要求使用者輸入一個值。

  • Mandatory=$True:這個屬性確保使用者在執行指令碼或函式時必須提供該參數,否則會顯示提示,防止因為缺少必要參數而導致的執行錯誤。
  • Position=0:這個屬性允許使用者不必明確指定參數名,而是直接通過位置提供參數值,這使指令碼更加簡潔和易用。

運作方式

當未提供 -computername 參數時,PowerShell 會自動提示用戶輸入該值。請看截圖:
https://ithelp.ithome.com.tw/upload/images/20241008/20168708ST9bVZHP60.png
這樣,PowerShell 通過互動提示確保使用者提供必要的參數,從而避免指令碼中因缺少參數而引發的執行錯誤。這種設計方式不僅提升了指令碼的可用性,還使得使用者更容易理解該參數的重要性。


20.4 增加參數的別名

使用 [Alias('')] 可以為目標參數新增替代名稱,也就是 **別名 **。別名的好處在於為參數提供替代名稱,使指令碼更加靈活,並且對使用者更加友好。這在使用者習慣了其他工具的命名方式,或者根據自身使用經驗期望不同的參數名稱時特別有用。

修正後的範例

[CmdletBinding()]
param (
    [Parameter(Mandatory=$True)]
    [Alias('host', 'server')]
    [string]$computername,

    [Parameter(Position=1)]
    [int]$drivetype = 3
)

Get-CimInstance -Class Win32_LogicalDisk -ComputerName $computername `
    -Filter "drivetype=$drivetype" | 
    Sort-Object -Property DeviceID |
    Select-Object -Property DeviceID,
        @{Label = 'FreeSpace(MB)'; Expression = { $_.FreeSpace / 1MB -as [int] } },
        @{Label = 'Size(GB)'; Expression = { $_.Size / 1GB -as [int] } },
        @{Label = '%Free'; Expression = { $_.FreeSpace / $_.Size * 100 -as [int] } }

為單一參數新增複數別名

在範例中,為參數 $computername 添加了多個別名 [Alias('host', 'server')]。這些別名允許使用者在呼叫指令碼時使用不同的名稱來指向同一個參數,因此可以使用以下方式執行 script

 .\chapter20_baseline.ps1 -host 'localhost'
 .\chapter20_baseline.ps1 -server 'localhost' 

20.5 驗證輸入的參數

透過 [ValidateSet()] 可以指定某個參數只接受特定範圍內的值,這樣可以確保指令碼只處理有效的資料,並且在使用者提供無效值時立即向其回饋錯誤訊息。這對於避免錯誤輸入並提高指令碼的穩健性和可預測性非常有用。

修正後的範例

[CmdletBinding()]
param (
    [Parameter(Mandatory=$True)]
    [Alias('host')]
    [string]$computername,

    [Parameter(Position=1)]
    [ValidateSet(2, 3)]
    [int]$drivetype = 3
)

Get-CimInstance -Class Win32_LogicalDisk -ComputerName $computername `
    -Filter "drivetype=$drivetype" | 
    Sort-Object -Property DeviceID |
    Select-Object -Property DeviceID,
        @{Label = 'FreeSpace(MB)'; Expression = { $_.FreeSpace / 1MB -as [int] } },
        @{Label = 'Size(GB)'; Expression = { $_.Size / 1GB -as [int] } },
        @{Label = '%Free'; Expression = { $_.FreeSpace / $_.Size * 100 -as [int] } }

運作方式

在範例中, [ValidateSet(2, 3)] 用於參數 $drivetype,這表示該參數只接受值 23。當使用者提供其他任何值時,PowerShell 會立刻顯示錯誤並提示有效的選擇。例如:
https://ithelp.ithome.com.tw/upload/images/20241008/20168708YCMAaaml5S.png
這種立即回饋的方式有效避免了不必要的錯誤執行,確保腳本只處理合理的輸入。

其他驗證屬性

除了 [ValidateSet()],PowerShell 還提供了其他一些有用的驗證屬性,這些屬性可以根據需求為參數的值提供不同形式的驗證,增加指令碼的可靠性。

[ValidateRange()]

  • 指定參數的值必須在某個數值範圍之內。
  • 例如,要確保參數 $drivesize 的值在 110 之間,可以使用:
[Parameter(Position=2)]
[ValidateRange(1, 10)]
[int]$drivesize

這樣,當使用者提供超出 1-10 的值時,PowerShell 會提示錯誤。
這樣,當使用者提供超出 1-10 的值時,PowerShell 會提示錯誤。

[ValidatePattern()]

  • 使用正則表達式來驗證參數的值是否符合某個特定的格式。
  • 例如,驗證 $username 必須符合以字母開頭並且只能包含字母或數字的格式,可以使用:
[Parameter(Position=3)]
[ValidatePattern('^[a-zA-Z][a-zA-Z0-9]*$')]
[string]$username

這樣,如果使用者提供不符合格式的值,例如包含特殊字符的名稱,PowerShell 會提示錯誤。


20.6 透過詳細輸出增加親和力和友好度

Write-Verbose 允許輸出詳細的操作訊息,這些訊息可以幫助使用者了解指令碼的內部運作。這些詳細信息只有在使用 -Verbose 參數時才會顯示,因此它不會影響指令碼的正常運行,但能在需要時提供有價值的診斷信息,從而提升指令碼的親和力和使用者體驗。

修正後的範例

[CmdletBinding()]
param (
    [Parameter(Mandatory=$True)]
    [Alias('host')]
    [string]$computername,

    [Parameter(Position=1)]
    [ValidateSet(2, 3)]
    [int]$drivetype = 3
)

Write-Verbose "Connecting to $computername"
Write-Verbose "Looking for drive type $drivetype"

# 執行主要命令
Get-CimInstance -Class Win32_LogicalDisk -ComputerName $computername `
    -Filter "drivetype=$drivetype" | 
    Sort-Object -Property DeviceID |
    Select-Object -Property DeviceID,
        @{Label = 'FreeSpace(MB)'; Expression = { $_.FreeSpace / 1MB -as [int] } },
        @{Label = 'Size(GB)'; Expression = { $_.Size / 1GB -as [int] } },
        @{Label = '%Free'; Expression = { $_.FreeSpace / $_.Size * 100 -as [int] } }

Write-Verbose "Finished running command"

運作方式

在這段範例中, Write-Verbose 用於輸出多個關鍵的執行步驟信息,例如:

  • "Connecting to $computername":提示目前正在連接目標電腦。
  • "Looking for drive type $drivetype":顯示正在查找的磁碟類型。
  • "Finished running command":告知使用者指令碼已完成操作。

這些詳細輸出可以在運行指令碼時使用 -Verbose 參數啟用,例如:

.\chapter20_baseline.ps1 -computername 'localhost' -Verbose

啟用 -Verbose 後,使用者會看到額外的執行訊息,這些訊息有助於了解指令碼的每一步動作,特別是在排查問題或需要了解指令碼執行流程時非常有幫助。

為什麼使用 Write-Verbose?

  1. 提高透明度Write-Verbose 允許你在指令碼中增加更多的訊息來解釋指令碼目前正在做什麼,這樣使用者就可以更加清楚每一步的操作。例如,在執行時間較長的指令碼中,Verbose 信息可以讓使用者了解當前執行的狀態,減少焦慮感。
  2. 便於調試和診斷:當使用者在執行指令碼時遇到問題,啟用 -Verbose 可以顯示更多的背景信息,便於理解腳本在不同步驟的執行情況,這有助於快速找到可能的問題點。
  3. 不影響正常輸出Write-Verbose 不會干擾指令碼的正常輸出結果,它只在需要時才會顯示額外信息。這使得它比 Write-Host 或其他輸出命令更適合用於診斷和詳細輸出。

明日主題

Day 25 - 使用正規表示式來解析文字檔


上一篇
Day 23 - 指令碼編寫 Part 2
下一篇
Day 25 - 使用正規表示式來解析文字檔
系列文
《30天挑戰精通 PowerShell:從 Windows Server 到 Azure DevOps 自動化之旅》30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言